﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Shell;
using System.Windows.Threading;
using RentIt06WPFClient.UserSession;

namespace RentIt06WPFClient {
    /// <summary>
    /// Interaction logic for Download.xaml
    /// </summary>
    public partial class Download : Window {
        private readonly Media media;
        private string dir;
        private string fileName;

        #region DownloadDir Code
        [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
        static extern int SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out string pszPath);

        private string getDefaultDownloadDir() {
            var downloads = new Guid("374DE290-123F-4565-9164-39C4925E467B");
            string result;
            SHGetKnownFolderPath(downloads, 0, IntPtr.Zero, out result);
            return result;
        }
        #endregion

        private static readonly Action EmptyDelegate = delegate { };

        public Download(Media m) {
            InitializeComponent();
            media = m;
            dir = getDefaultDownloadDir();
            Title = media.Title;
            fileName = media.Filename;
            FileNameTextBox.Text = dir + "\\" + media.Filename;
        }

        private void ChangeButton_Click(object sender, RoutedEventArgs e) {
            // Split the filename at "." and extract the name and extension.
            var split = fileName.Split(new[] {'.'});
            var name = string.Concat(split.Take(split.Length - 1));
            var ext = split.Last();

            // Open a SaveFileDialog with the name and extension
            var dlg = new Microsoft.Win32.SaveFileDialog {
                FileName = name,
                DefaultExt = ext,
                InitialDirectory = dir
            };
            var result = dlg.ShowDialog();

            // Get the selected file name / path and display it in the TextBox
            if (result != true) return;
            this.FileNameTextBox.Text = dlg.FileName;
            var totalPath = this.media.Filename.Split(new[] { '\\' });
            this.dir = string.Concat(totalPath.Take(totalPath.Length - 1));
            this.fileName = totalPath.Last();
        }

        private void DownloadButton_Click(object sender, RoutedEventArgs e) {
            // Get a Download token that will allow us to get the stream
            var token = Session.User.GetDownloadToken(media.Id);
            // Get the stream
            var readStream = Session.FileService.DownloadMedia(token, media.Id);
            // Prepare the stream to the file on disk
            var writeStream = new FileStream(FileNameTextBox.Text, FileMode.Create);

            // Disable the buttons and show the download text
            DownloadText.Visibility = Visibility.Visible;
            DownloadButton.IsEnabled = false;
            DownloadButton.Content = "In progress";
            ChangeButton.IsEnabled = false;

            // Set the file length in the progress bar and initialize progress labels
            DownloadBar.Maximum = media.FileLenght;
            BytesTotal.Content = media.FileLenght + " B";
            BytesDone.Content = "0";

            // Show win7 taskbar progressbar
            TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Normal;

            var calc = new SpeedCalculator(100,20);

            // Start the download
            const int bufferLen = 65000;
            var buffer = new byte[bufferLen];
            int count;
            while ((count = readStream.Read(buffer, 0, bufferLen)) > 0) {
                writeStream.Write(buffer, 0, count);
                calc.Input(readStream.Position);
                Speed.Content = calc.FormatedSpeed;
                UpdateProgressBar(readStream.Position);
            }

            // show that the donwnload is complete
            DownloadButton.Content = "Completed!";
            TaskbarItemInfo.ProgressState = TaskbarItemProgressState.None;
            Title += "  Download complete!";
            Speed.Content = "";
            Dispatcher.Invoke(DispatcherPriority.Background, EmptyDelegate);
        }

        private void UpdateProgressBar(long position) {
            DownloadBar.Value = position;
            BytesDone.Content = position;
            var pct = (double)position/media.FileLenght;
            TaskbarItemInfo.ProgressValue = pct;
            Title = media.Title + "   " + (int)(pct*100) + "%";
            Dispatcher.Invoke(DispatcherPriority.Background, EmptyDelegate);
        }
        
        public class SpeedCalculator {
            public double Speed { get; private set; }
            public string FormatedSpeed { get {
                if (Speed < 1000) return string.Format("{0:0.0} B/s ", Speed);
                if (Speed >= 1000) return string.Format("{0:0.0} kB/s", (Speed / 1000.0));
                return "";
            } }

            private bool stop;
            private struct Entry {
                public long Bytes;
                public DateTime Time;
            }
            private readonly SynchronizedCollection<Entry> inputLog = new SynchronizedCollection<Entry>();
            private readonly Queue<double> speedLog = new Queue<double>(); 
            private readonly int frequency, interval;
            /// <param name="frequency">Specifies in ms how often the speed will be updated</param>
            /// <param name="interval">Specifies the amount of intervals that are used to calculate the speed,
            /// a interval's lenght is defined by the frequency.</param>
            public SpeedCalculator(int frequency = 1000, int interval = 5) {
                this.frequency = frequency;
                this.interval = interval;
                ThreadPool.QueueUserWorkItem(UpdateState);
            }
            ~SpeedCalculator() {
                stop = true;
            }

            private void UpdateState(object o) {
                while(!stop) {
                    Thread.Sleep(frequency);
                    if (inputLog.Count > 1) {
                        speedLog.Enqueue(CalculateSpeed(inputLog.First(), inputLog.Last()));
                        inputLog.Clear();
                        if (speedLog.Count > interval) speedLog.Dequeue();
                        Speed = speedLog.Average();
                    } else Speed = 0;
                }
            }

            public void Input(long bytes) {
                inputLog.Add(new Entry { Bytes = bytes, Time = DateTime.Now });
            }
            
            /// result is in bytes/secs
            private double CalculateSpeed(Entry from, Entry to) {
                return (from.Bytes - to.Bytes)/(from.Time - to.Time).TotalSeconds;
            }
        }          
    }

}
